﻿// Decompiled with JetBrains decompiler
// Type: Netick.CustomizableSocketTransport
// Assembly: Netick, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 6726ECA1-C773-4CF7-8952-E81B30D93B52
// Assembly location: D:\WorkSpaceDemo\UPM\NetickForUnity\Netick\Runtime\Netick.dll
// XML documentation location: D:\WorkSpaceDemo\UPM\NetickForUnity\Netick\Runtime\Netick.xml

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;

#nullable disable
namespace Netick;

public class CustomizableSocketTransport
{
  internal int[] Mtus = new int[5]
  {
    500,
    1000,
    1200,
    1300,
    1400
  };
  public readonly int MaxConnections;
  public double Timeout = 10.0;
  public readonly double TickPeriod = 1.0;
  /// ----------------------------------------------------
  private unsafe byte* _connectRequestBuffer;
  private int _connectRequestSize;
  private BitBuffer _writeBitBuffer = new BitBuffer();
  private BitBuffer _readBitBuffer = new BitBuffer(createChunks: false);
  private unsafe byte* _buffer;
  private IntPtr _bufferPtr;
  private int _bufferSize = 2048 /*0x0800*/;
  private INetickSocket _mySocket;
  private bool _isServer;
  private Queue<Connection> _connectionsPool = new Queue<Connection>(128 /*0x80*/);
  private Dictionary<IEndPoint, Connection> _connections = new Dictionary<IEndPoint, Connection>(128 /*0x80*/);
  private Dictionary<IEndPoint, PendingConnection> _pendingConnections = new Dictionary<IEndPoint, PendingConnection>(128 /*0x80*/);
  private double _mtuPeriod = 0.2;
  private readonly double _heartbeatPeriod = 1.0;
  private double _lastTime;
  internal double _currentTime;
  internal Stopwatch _timer = new Stopwatch();
  private bool _isInitialized;
  private List<Connection> _disconnectedCache;
  private List<IEndPoint> _pendingConnectionsRemovedCache = new List<IEndPoint>(128 /*0x80*/);
  private NetworkConnectionRequest _request = new NetworkConnectionRequest();

  public event OnConnectionRequest OnConnectRequestEvent;

  public event OnConnectFailed OnConnectFailedEvent;

  public event OnPeerConnected PeerConnectedEvent;

  public event OnPeerDisconnected PeerDisconnectedEvent;

  public event OnNetworkReceive NetworkReceiveEvent;

  public bool IsServer => this._isServer;

  public unsafe CustomizableSocketTransport(INetickSocket socket, int maxConnections = 64 /*0x40*/)
  {
    this._mySocket = socket;
    this.MaxConnections = maxConnections;
    this._disconnectedCache = new List<Connection>(this.MaxConnections);
    for (int index = 0; index < this.MaxConnections; ++index)
      this._connectionsPool.Enqueue(new Connection(this, index));
    this._buffer = (byte*) MemoryAllocation.Malloc((long) this._bufferSize);
    this._bufferPtr = new IntPtr((void*) this._buffer);
    this._connectRequestBuffer = (byte*) MemoryAllocation.Malloc(300L);
  }

  unsafe ~CustomizableSocketTransport()
  {
    this.Stop();
    MemoryAllocation.Free((void*) this._buffer);
    MemoryAllocation.Free((void*) this._connectRequestBuffer);
  }

  private void Initialize()
  {
    this._isInitialized = true;
    this._mySocket.Initialize();
  }

  public void Stop()
  {
    this._pendingConnections.Clear();
    this._disconnectedCache.Clear();
    this._pendingConnectionsRemovedCache.Clear();
    foreach (Connection connection in this._connections.Values)
      this._disconnectedCache.Add(connection);
    foreach (Connection conn in this._disconnectedCache)
      this.HandleDisconnect(conn, false, TransportDisconnectReason.Shutdown);
    if (this._mySocket == null)
      return;
    this._mySocket.Shutdown();
    this._isInitialized = false;
    this._mySocket.Deinitialize();
    this._mySocket = (INetickSocket) null;
  }

  public void StartClient(int sendBufferSize, int receiveBufferSize)
  {
    if (!this._isInitialized)
      this.Initialize();
    this._isServer = false;
    this._currentTime = this._timer.Elapsed.TotalMilliseconds / 1000.0;
    this._timer.Reset();
    this._timer.Start();
    this._mySocket.StartClient((long) sendBufferSize, (long) receiveBufferSize);
  }

  public void StartServer(int port, int sendBufferSize, int receiveBufferSize)
  {
    if (!this._isInitialized)
      this.Initialize();
    this._isServer = true;
    this._currentTime = this._timer.Elapsed.TotalMilliseconds / 1000.0;
    this._timer.Reset();
    this._timer.Start();
    this._mySocket.StartServer(port, (long) sendBufferSize, (long) receiveBufferSize);
  }

  public unsafe void Connect(
    string ip,
    int port,
    byte[] connectRequest,
    int connectRequestSizeBytes)
  {
    IEndPoint fromIpPort = this._mySocket.CreateFromIpPort(ip, (int) (ushort) port);
    this._mySocket.Connect(ref fromIpPort);
    if (this._pendingConnections.TryGetValue(fromIpPort, out PendingConnection _))
    {
      this._pendingConnections[fromIpPort] = new PendingConnection()
      {
        Address = fromIpPort,
        LastConnectionAttemptTime = this._currentTime
      };
    }
    else
    {
      this._connectRequestSize = connectRequest != null ? connectRequestSizeBytes : 0;
      if (connectRequest != null)
      {
        for (int index = 0; index < connectRequestSizeBytes; ++index)
          this._connectRequestBuffer[index] = connectRequest[index];
      }
      this.SendConnectRequest(this._writeBitBuffer, this._connectRequestBuffer, this._connectRequestSize, ref fromIpPort);
      PendingConnection pendingConnection = new PendingConnection()
      {
        Address = fromIpPort,
        LastConnectionAttemptTime = this._currentTime
      };
      this._pendingConnections.Add(fromIpPort, pendingConnection);
    }
  }

  public void Kick(Connection connection)
  {
    if (!this._connections.ContainsValue(connection))
      return;
    this.HandleDisconnect(connection, false, TransportDisconnectReason.Kick);
  }

  public void Update()
  {
    this._currentTime = this._timer.Elapsed.TotalMilliseconds / 1000.0;
    double num = this._currentTime - this._lastTime;
    this.Poll();
    double tickPeriod = this.TickPeriod;
    if (num < tickPeriod)
      return;
    this.Tick();
    this._lastTime = this._currentTime;
  }

  private unsafe void Tick()
  {
    foreach (PendingConnection pendingConnection in this._pendingConnections.Values)
    {
      IEndPoint address = pendingConnection.Address;
      this.SendConnectRequest(this._writeBitBuffer, this._connectRequestBuffer, this._connectRequestSize, ref address);
      if (this._currentTime - pendingConnection.LastConnectionAttemptTime > this.Timeout)
      {
        this._pendingConnectionsRemovedCache.Add(address);
        OnConnectFailed connectFailedEvent = this.OnConnectFailedEvent;
        if (connectFailedEvent != null)
          connectFailedEvent(address, ConnectionFailedReason.Timeout);
      }
    }
    foreach (IEndPoint key in this._pendingConnectionsRemovedCache)
      this._pendingConnections.Remove(key);
    this._pendingConnectionsRemovedCache.Clear();
    this._disconnectedCache.Clear();
    foreach (Connection connection in this._connections.Values)
    {
      if (!connection._mtuDone && this._currentTime - connection._lastMtuTime > this._mtuPeriod)
      {
        this.SendMtuDiscovery(connection, this._writeBitBuffer, connection._mtuLevel + 1);
        connection._lastMtuTime = this._currentTime;
        --connection._mtuSends;
        if (connection._mtuSends <= 0)
          connection._mtuDone = true;
      }
      if (this._currentTime - connection._lastRecvTime > this.Timeout)
        this._disconnectedCache.Add(connection);
      else if (this._currentTime - connection._lastSendTime > this._heartbeatPeriod)
        this.SendHeartbeat(connection, this._writeBitBuffer);
    }
    foreach (Connection conn in this._disconnectedCache)
      this.HandleDisconnect(conn, false, TransportDisconnectReason.Timeout);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private void HandleDisconnect(
    Connection conn,
    bool isRemote,
    TransportDisconnectReason disconnectReason)
  {
    if (disconnectReason != TransportDisconnectReason.Shutdown || isRemote)
    {
      OnPeerDisconnected disconnectedEvent = this.PeerDisconnectedEvent;
      if (disconnectedEvent != null)
        disconnectedEvent(conn, disconnectReason);
    }
    if (!isRemote)
      this.SendDisconnect(conn, this._writeBitBuffer, disconnectReason);
    this._connections.Remove(conn._address);
    this._connectionsPool.Enqueue(conn);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private unsafe void Poll()
  {
    if (this._mySocket.Poll() <= 0)
      return;
    IEndPoint address;
    int num;
    while ((num = this._mySocket.Receive(out address, (byte*) (void*) this._bufferPtr, this._bufferSize)) > 0)
    {
      this._readBitBuffer.SetFrom(this._buffer, num, this._bufferSize);
      this.ReadPacket(this._readBitBuffer, address, num);
    }
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private unsafe void SendConnectRequest(
    BitBuffer buffer,
    byte* request,
    int size,
    ref IEndPoint address)
  {
    this.WritePacket(InternalMsg.ConnectionRequest, buffer);
    int num = Math.Min(size, 300);
    buffer.AddInt(num);
    for (int index = 0; index < num; ++index)
      buffer.AddByte(request[index]);
    this._mySocket.Send(address, buffer._chunksByte, buffer.Length());
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private void SendConnectAccept(Connection connection, BitBuffer buffer)
  {
    this.WritePacket(InternalMsg.ConnectionAccepted, buffer);
    this.InternalSend(connection, buffer);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  public void SendPayload(Connection connection, BitBuffer buffer)
  {
    this.InternalSend(connection, buffer);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  public unsafe void SendPayload(Connection connection, IntPtr ptr, int size)
  {
    connection._lastSendTime = this._currentTime;
    this._mySocket.Send(connection._address, (byte*) (void*) ptr, size);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private void SendHeartbeat(Connection connection, BitBuffer buffer)
  {
    this.WritePacket(InternalMsg.Heartbeat, buffer);
    this.InternalSend(connection, buffer);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private unsafe void SendMtuDiscovery(Connection connection, BitBuffer buffer, int level)
  {
    this.WritePacket(InternalMsg.MtuDiscovery, buffer);
    buffer.Add(8, (uint) level);
    connection._lastSendTime = this._currentTime;
    this._mySocket.Send(connection._address, buffer._chunksByte, this.Mtus[level]);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private void SendMtuResponse(Connection connection, BitBuffer buffer, int mtuLevel)
  {
    this.WritePacket(InternalMsg.MtuResponse, buffer);
    buffer.Add(32 /*0x20*/, (uint) mtuLevel);
    this.InternalSend(connection, buffer);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private void SendDisconnect(
    Connection connection,
    BitBuffer buffer,
    TransportDisconnectReason disconnectReason)
  {
    this.WritePacket(InternalMsg.Disconnect, buffer);
    buffer.Add(32 /*0x20*/, (uint) (byte) disconnectReason);
    this.InternalSend(connection, buffer);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private unsafe void InternalSend(Connection to, BitBuffer buffer)
  {
    to._lastSendTime = this._currentTime;
    this._mySocket.Send(to._address, buffer._chunksByte, buffer.Length());
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private void ReadPacket(BitBuffer buffer, IEndPoint address, int dataLen)
  {
    switch (buffer.Read(8))
    {
      case 0:
        Connection peer;
        if (!this._connections.TryGetValue(address, out peer))
          break;
        peer._lastRecvTime = this._currentTime;
        OnNetworkReceive networkReceiveEvent = this.NetworkReceiveEvent;
        if (networkReceiveEvent == null)
          break;
        networkReceiveEvent(peer, buffer);
        break;
      case 1:
        Connection connection1;
        if (!this._connections.TryGetValue(address, out connection1))
          break;
        connection1._lastRecvTime = this._currentTime;
        break;
      case 2:
        Connection connection2;
        if (!this._connections.TryGetValue(address, out connection2))
          break;
        int mtuLevel = Math.Min(this.Mtus.Length - 1, (int) buffer.Read(32 /*0x20*/));
        this.SendMtuResponse(connection2, this._writeBitBuffer, mtuLevel);
        break;
      case 3:
        Connection connection3;
        if (!this._connections.TryGetValue(address, out connection3))
          break;
        int index1 = Math.Min(this.Mtus.Length - 1, (int) buffer.Read(32 /*0x20*/));
        if (index1 <= connection3._mtuLevel)
          break;
        connection3._mtuLevel = index1;
        if (connection3._mtuLevel >= this.Mtus.Length - 1)
        {
          connection3._mtuLevel = this.Mtus.Length - 1;
          connection3._mtuDone = true;
        }
        connection3._mtu = this.Mtus[index1];
        break;
      case 4:
        if (!this._isServer || this._connections.Count >= this.MaxConnections)
          break;
        bool flag = false;
        Connection connection4;
        if (!this._connections.TryGetValue(address, out connection4))
        {
          this._request.AllowConnection = true;
          this._request.Source = address;
          this._request.DataLength = buffer.ReadInt();
          for (int index2 = 0; index2 < this._request.DataLength; ++index2)
            this._request.Data[index2] = buffer.ReadByte();
          this.OnConnectRequestEvent(this._request);
          flag = this._request.AllowConnection;
          if (flag)
          {
            connection4 = this._connectionsPool.Dequeue();
            connection4.Init(address);
            this._connections.Add(address, connection4);
            OnPeerConnected peerConnectedEvent = this.PeerConnectedEvent;
            if (peerConnectedEvent != null)
              peerConnectedEvent(connection4);
          }
        }
        if (!flag)
          break;
        this.SendConnectAccept(connection4, this._writeBitBuffer);
        this.SendMtuDiscovery(connection4, this._writeBitBuffer, 2);
        break;
      case 5:
        if (this._isServer || !this._pendingConnections.ContainsKey(address))
          break;
        this._pendingConnections.Remove(address);
        Connection connection5;
        if (this._connections.TryGetValue(address, out connection5))
          break;
        connection5 = this._connectionsPool.Dequeue();
        connection5.Init(address);
        this._connections.Add(address, connection5);
        OnPeerConnected peerConnectedEvent1 = this.PeerConnectedEvent;
        if (peerConnectedEvent1 != null)
          peerConnectedEvent1(connection5);
        this.SendMtuDiscovery(connection5, this._writeBitBuffer, 2);
        break;
      case 6:
        TransportDisconnectReason disconnectReason = (TransportDisconnectReason) buffer.Read(8);
        Connection conn;
        if (!this._connections.TryGetValue(address, out conn))
          break;
        this.HandleDisconnect(conn, true, disconnectReason);
        break;
    }
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private void WritePacket(InternalMsg msg, BitBuffer buffer)
  {
    buffer.Clear();
    buffer.Add(8, (uint) msg);
  }
}
